package com.icas.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.icas.entity.resultentity.Result;
import com.icas.util.jwt.JwtToken;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * 继承Shiro内置的BasicHttpAuthenticationFilter
 *
 * 需要重新的方法有：
 * 1. isAccessAllowed 是否允许访问
 * 2. isLoginAttempt 判断用户是否想要登入
 * 3. executeLogin
 * 4. preHandle
 * @Author: tocchen
 * @Date: 2022/4/3 20:48
 *
 **/
public class JwtFilter extends BasicHttpAuthenticationFilter {

    /**
     * 是否运行访问;带Token则对Token进行验证，没有则直接通过
     * @param request 当前的 HTTP servlet 请求
     * @param response 当前的 HTTP servlet 响应
     * @param mappedValue 配置为字符串的 HTTP 方法数组。如果未配置任何方法，则为空
     * @return 返回Boolean值
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //判断请求的请求头是否带上 "Token"
        if (isLoginAttempt(request, response)){
            //如果存在，则进入 executeLogin 方法执行登入，检查 token 是否正确
            try{
                executeLogin(request, response);
            }catch (Exception e){
                responseError(response,401,e.getMessage());
                return false;
            }
            return true;
        }
        //如果请求头不存在 Token，则可能是执行登陆操作或者是游客状态访问，无需检查 token，直接返回 true
        return true;

    }

    /**
     * 判断是否是登录请求
     * @param request incoming ServletRequest
     * @param response outgoing ServletResponse
     * @return 返回结果
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req= (HttpServletRequest) request;
        String token=req.getHeader("token");
        return token!=null;
    }



    /**
     * 重写该方法，是为了实现对默认的UsernamePasswordToken的创建方式进行修改
     * @param request incoming ServletRequest
     * @param response outgoing ServletResponse
     * @return Boolean
     * @throws AuthenticationException AuthenticationException
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws AuthenticationException {
        HttpServletRequest req= (HttpServletRequest) request;
        String token=req.getHeader("token");
        JwtToken jwt=new JwtToken(token);
        //交给自定义的realm对象去登录，如果错误他会抛出异常并被捕获
        SecurityUtils.getSubject().login(jwt);
        return true;
    }

    /**
     * 解决跨域问题
     * @param request incoming ServletRequest
     * @param response outgoing ServletResponse
     * @return Boolean
     * @throws Exception exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws AuthenticationException {
        HttpServletRequest req= (HttpServletRequest) request;
        HttpServletResponse res= (HttpServletResponse) response;
        res.setHeader("Access-control-Allow-Origin",req.getHeader("Origin"));
        res.setHeader("Access-control-Allow-Methods","GET,POST,OPTIONS,PUT,DELETE");
        res.setHeader("Access-control-Allow-Headers",req.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求，这里我们给option请求直接返回正常状态
        if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
            res.setStatus(HttpStatus.OK.value());
            return false;
        }
        try{
            return super.preHandle(request, response);
        }catch (Exception e){
            responseError(response,401,e.getMessage());
            return false;
        }
    }

    /**
     * 设置非法请求的重定向问题；非法请求后将跳转至/unauthorized/
     * @param response incoming ServletRequest
     * @param message outgoing ServletResponse
     */
    private void responseError(ServletResponse response,int code , String message) {
        try {

            ServletOutputStream outputStream = response.getOutputStream();

            outputStream.write(new ObjectMapper().writeValueAsBytes(Result.error(code,message)));

            outputStream.flush();

            outputStream.close();

        } catch (Exception e) {
            System.out.println("重定向异常");
            System.out.println(e.getMessage());
        }
    }
}
